package com.zdp.controller;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.zdp.feign.StockFeignClient;
import com.zdp.service.TestService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.List;

/**
 * @author sesshomaru
 * @date 2021/7/26 20:13
 */
@Slf4j
@RestController
public class TestController {

    @Autowired
    private TestService testService;


    @GetMapping("/test-a")
    public String testA() {
        testService.common();
        return "test-a";
    }

    @GetMapping("/test-b")
    public String testB() {
        testService.common();
        return "test-b";
    }

    @GetMapping("/test-add-flow-rule")
    public String testAddFlowRule() {
        initFlowQpsRule();
        return "success";
    }

    // 代码的方式实现sentinel控制台中配置的一些规则
    private void initFlowQpsRule() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule flowRule = new FlowRule("/codeing-FlowQps");
        // set limit qps to 20
        flowRule.setCount(20);
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        flowRule.setLimitApp("default");
        rules.add(flowRule);
        FlowRuleManager.loadRules(rules);
    }

    /**
     * 使用sentinel的api保护接口
     * 更优雅的方式是通过注解的方式保护接口
     * @param a
     * @return
     */
    @GetMapping("/test-sentinel-api")
    public String testSentinelApi(@RequestParam(required = false) String a) {
        String resourceName = "test-sentinel-api";
        // 配置一下针对来源
        // ContextUtil.enter(resourceName, "test-wfw");

        Entry entry = null;
        try {
            // 定义一个sentinel保护的资源,  名称是test-sentinel-api
            entry = SphU.entry(resourceName);
            // TODO 业务begin
            // 模拟业务异常
            if (StringUtils.isBlank(a)) {
                throw new IllegalArgumentException("a不能为空");
            }
            // 被保护的业务逻辑
            // TODO 业务end
            return a;
        } catch (BlockException e) {
           // 资源访问阻止，被限流或被降级
            // 在此处进行相应的处理操作
            log.warn("限流，或者降级了");
            return "限流，或者降级了";

        } catch (Exception ex) {
            // 统计IllegalArgumentException，异常次数，异常占比......
            // 若需要配置降级规则，需要通过这种方式记录业务异常
            Tracer.traceEntry(ex, entry);
            return "参数非法";
        }
        finally {
            // 务必保证 exit，务必保证每个 entry 与 exit 配对
            if (entry != null) {
                entry.exit();
            }
            //ContextUtil.exit();
        }
    }

    @GetMapping("/test-sentinel-annotation")
    @SentinelResource(value = "test-sentinel-annotation",
            blockHandler = "blockHandlerTestSentinelAnnotation",
            fallback = "helloFallback"
            )
    public String testSentinelAnnotation(@RequestParam(required = false) String id) {
        // 模拟业务异常
        if (StringUtils.isBlank(id)) {
            throw new IllegalArgumentException("发生业务异常,a不能为空");
        }
        return String.format("Hello at %d", id);
    }

    // Block 异常处理函数，参数最后多一个 BlockException，其余与原函数一致.
    // 若 blockHandler 和 fallback 都进行了配置，则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑
    // 专门用来处理限流、降级
    public String blockHandlerTestSentinelAnnotation(String id, BlockException ex){
        ex.printStackTrace();
        return "Oops, error occurred at " + id;
    }

    // fallback 函数名称，可选项，用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常（除了 exceptionsToIgnore 里面排除掉的异常类型）进行处理
    // 如何@SentinelResource配置了Fallback就可以用来处理业务异常
    public String helloFallback(String id, Throwable te) {
        te.printStackTrace();
        return String.format("Halooooo %d", id);
    }


    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/test-sentinel-restTemplate")
    public String testSentinelRestTemplate(String productId) {
        return restTemplate.getForObject("http://stock-server/stock/reduce/" + productId, String.class);
    }

    @Autowired
    private StockFeignClient stockFeignClient;

    @GetMapping("/test-sentinel-feign")
    public String testSentinelFeign(Integer productId) {
        log.info("productId:{}", productId);
        return stockFeignClient.reduce(productId);
    }
}
